<template>
  <div class="card-container">
    <div class="card-container">
      <!-- 功能菜单 -->
      <div class="process-designer__header">
        <slot name="control-header" />
        <el-button-group key="file-control">
          <el-button size="mini" type="primary" icon="el-icon-folder-opened" @click="$refs.refFile.click()">打开文件</el-button>
          <el-tooltip effect="light">
            <div slot="content">
              <el-button size="mini" type="text" @click="downloadProcessAsXml()">下载为XML文件</el-button>
              <br />
              <el-button size="mini" type="text" @click="downloadProcessAsSvg()">下载为SVG文件</el-button>
              <br />
              <el-button size="mini" type="text" @click="downloadProcessAsBpmn()">下载为BPMN文件</el-button>
            </div>
            <el-button size="mini" type="primary" icon="el-icon-download">下载文件</el-button>
          </el-tooltip>
<!--           <el-tooltip effect="light"> -->
<!--             <div slot="content"> -->
<!--               <el-button size="mini" type="text" @click="previewProcessXML">预览XML</el-button> -->
<!--               <br /> -->
<!--               <el-button size="mini" type="text" @click="previewProcessJson">预览JSON</el-button> -->
<!--             </div> -->
<!--             <el-button size="mini" type="primary" icon="el-icon-view">预览</el-button> -->
<!--           </el-tooltip> -->
<!--           <el-tooltip v-if="simulation" effect="light" :content="simulationStatus ? '退出模拟' : '开启模拟'"> -->
<!--             <el-button size="mini" type="primary" icon="el-icon-cpu" @click="processSimulation"> -->
<!--               模拟 -->
<!--             </el-button> -->
<!--           </el-tooltip> -->
        </el-button-group>
        <el-button-group key="align-control">
          <el-tooltip effect="light" content="向左对齐">
            <el-button size="mini" class="align align-left" icon="el-icon-s-data" @click="elementsAlign('left')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向右对齐">
            <el-button size="mini" class="align align-right" icon="el-icon-s-data" @click="elementsAlign('right')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向上对齐">
            <el-button size="mini" class="align align-top" icon="el-icon-s-data" @click="elementsAlign('top')" />
          </el-tooltip>
          <el-tooltip effect="light" content="向下对齐">
            <el-button size="mini" class="align align-bottom" icon="el-icon-s-data" @click="elementsAlign('bottom')" />
          </el-tooltip>
          <el-tooltip effect="light" content="水平居中">
            <el-button size="mini" class="align align-center" icon="el-icon-s-data" @click="elementsAlign('center')" />
          </el-tooltip>
          <el-tooltip effect="light" content="垂直居中">
            <el-button size="mini" class="align align-middle" icon="el-icon-s-data" @click="elementsAlign('middle')" />
          </el-tooltip>
        </el-button-group>
        <el-button-group key="scale-control">
          <el-tooltip effect="light" content="缩小视图">
            <el-button size="mini" :disabled="defaultZoom < 0.2" icon="el-icon-zoom-out" @click="processZoomOut()" />
          </el-tooltip>
          <el-button size="mini">{{ Math.floor(defaultZoom * 10 * 10) + "%" }}</el-button>
          <el-tooltip effect="light" content="放大视图">
            <el-button size="mini" :disabled="defaultZoom > 4" icon="el-icon-zoom-in" @click="processZoomIn()" />
          </el-tooltip>
          <el-tooltip effect="light" content="重置视图并居中">
            <el-button size="mini" icon="el-icon-c-scale-to-original" @click="processReZoom()" />
          </el-tooltip>
        </el-button-group>
        <el-button-group key="stack-control">
          <el-tooltip effect="light" content="撤销">
            <el-button size="mini" :disabled="!revocable" icon="el-icon-refresh-left" @click="processUndo()" />
          </el-tooltip>
          <el-tooltip effect="light" content="恢复">
            <el-button size="mini" :disabled="!recoverable" icon="el-icon-refresh-right" @click="processRedo()" />
          </el-tooltip>
          <el-tooltip effect="light" content="重新绘制">
            <el-button size="mini" icon="el-icon-refresh" @click="processRestart" />
          </el-tooltip>
        </el-button-group>
        <!-- 用于打开本地文件-->
        <input id="files" ref="refFile" type="file" style="display: none" accept=".xml, .bpmn" @change="importLocalFile" />
      </div>
<!--       <el-dialog title="预览" width="60%" :visible.sync="previewModelVisible" append-to-body destroy-on-close> -->
<!--         <highlightjs :language="previewType" :code="previewResult" /> -->
<!--       </el-dialog> -->
    </div>
    <!-- 编辑区 -->
    <div ref="containers" class="containers">
      <div ref="canvas" class="canvas" />
      <div id="js-properties-panel" />
    </div>
  </div>
</template>

<script>
import BpmnModeler from 'bpmn-js/lib/Modeler' // 引入 bpmn-js
import customTranslate from './customTranslate/customTranslate' // 汉化
import xmlStr from './xml' // 引入默认显示的xml字符串数据
// import axios from 'axios'
// 引入json转换与高亮
import X2JS from 'x2js'

// 右侧属性栏功能
import propertiesPanelModule from 'bpmn-js-properties-panel'
import propertiesProviderModule from 'bpmn-js-properties-panel/lib/provider/camunda'
import camundaModdleDescriptor from 'camunda-bpmn-moddle/resources/camunda'
import AddExporterModule from '@bpmn-io/add-exporter'
import BpmnColorPickerModule from 'bpmn-js-color-picker'

export default {
  name: 'Bpmn2',
  data() {
    return {
      bpmnModeler: null,
      containers: null,
      canvas: null,
      customTranslateModule: {
        translate: ['value', customTranslate]
      },
      // events: ["element.click"],
      defaultZoom: 1,
      previewModelVisible: false,
      simulation: true,
      simulationStatus: false,
      previewResult: '',
      previewType: 'xml',
      recoverable: false,
      revocable: false
    }
  },
  mounted() {
    this.initDiagram()
  },
  methods: {
    // 初始化方法
    initDiagram() {
      this.containers = this.$refs.containers // 获取到属性ref为“containers”的dom节点
      const canvas = this.$refs.canvas // 获取到属性ref为“canvas”的dom节点
      this.bpmnModeler = new BpmnModeler({
        container: canvas,
        // 添加控制板
        propertiesPanel: {
          parent: '#js-properties-panel'
        },
        // 左侧
        additionalModules: [
          this.customTranslateModule,
          AddExporterModule,
          BpmnColorPickerModule,
          // 右边的属性栏
          propertiesProviderModule,
          propertiesPanelModule
        ],
        moddleExtensions: {
          camunda: camundaModdleDescriptor
        },
        exporter: {
          name: 'MyBPM (https://gitee.com/liulufeng/MyBPM)',
          version: '1.0.0'
        }
      })
      this.initModelListeners()
      this.createNewDiagram()
    },
    initModelListeners() {
      const EventBus = this.bpmnModeler.get('eventBus')
      /*
      const that = this
      // 注册需要的监听事件, 将. 替换为 - , 避免解析异常
      this.events.forEach(event => {
        EventBus.on(event, function(eventObj) {
          const eventName = event.replace(/\./g, '-')
          const element = eventObj ? eventObj.element : null
          that.$emit(eventName, element, eventObj)
        })
      })
      */
      // 监听图形改变返回xml
      EventBus.on('commandStack.changed', async event => {
        try {
          this.recoverable = this.bpmnModeler.get('commandStack').canRedo()
          this.revocable = this.bpmnModeler.get('commandStack').canUndo()
          const { xml } = await this.bpmnModeler.saveXML({ format: true })
          this.$emit('commandStack-changed', event)
          this.$emit('input', xml)
          this.$emit('change', xml)
        } catch (e) {
          console.error(`[Process Designer Warn]: ${e.message || e}`)
        }
      })
      // 监听视图缩放变化
      this.bpmnModeler.on('canvas.viewbox.changed', ({ viewbox }) => {
        this.$emit('canvas-viewbox-changed', { viewbox })
        const { scale } = viewbox
        this.defaultZoom = Math.floor(scale * 100) / 100
      })
    },
    // 注意：必须先加载一个bpmn文件，新建就是加载一个空的bpmn文件，否则不能拖拽节点
    createNewDiagram() {
      /**
       * 获取后台，获取默认的xml
       * */
      /*
      var diagramUrl = 'https://github.moeyy.xyz/https://github.com/bpmn-io/bpmn-js-examples/blob/main/starter/diagram.bpmn'
      axios({
          method: 'GET',
          url: diagramUrl
      }).then((res)=>{
          console.log(res.data)
          this.openDiagram(res.data)
      }).catch((err)=>{
          console.log(err)
      })
      */

      this.openDiagram(xmlStr)
    },
    async openDiagram(xml) {
      /**
       * 导入xml（字符串形式），返回导入结果
       * 后续会取消传入回调函数的方式
       * 推荐使用async/await或者链式调用
       * @param { string } xml 流程图xml字符串
       * @param { Promise } callback 回调函数，出错时返回{ warnings，err }
       */
      try {
        const result = await this.bpmnModeler.createDiagram()
        // const result = await this.bpmnModeler.importXML(xml) // 使用自定义的xml
        const { warnings } = result
        console.log(warnings)
      } catch (err) {
        console.log(err.message, err.warnings)
      }
    },
    /**
     * 下载流程图到本地
     *
     * @param {string} type
     * @param {*} name
     */
    async downloadProcess(type, name) {
      try {
        const _this = this
        // 按需要类型创建文件并下载
        if (type === 'xml' || type === 'bpmn') {
          const { err, xml } = await this.bpmnModeler.saveXML({ format: true })
          // 读取异常时抛出异常
          if (err) {
            console.error(`[Process Designer Warn ]: ${err.message || err}`)
          }
          const { href, filename } = _this.setEncoded(type, name, xml)
          downloadFunc(href, filename)
        } else {
          const { err, svg } = await this.bpmnModeler.saveSVG()
          // 读取异常时抛出异常
          if (err) {
            return console.error(err)
          }
          const { href, filename } = _this.setEncoded('svg', name, svg)
          downloadFunc(href, filename)
        }
      } catch (e) {
        console.error(`[Process Designer Warn ]: ${e.message || e}`)
      }
      // 文件下载方法
      function downloadFunc(href, filename) {
        if (href && filename) {
          const a = document.createElement('a')
          a.download = filename // 指定下载的文件名
          a.href = href //  URL对象
          a.click() // 模拟点击
          URL.revokeObjectURL(a.href) // 释放URL 对象
        }
      }
    },
    // 根据所需类型进行转码并返回下载地址
    setEncoded(type, filename = 'diagram', data) {
      const encodedData = encodeURIComponent(data)
      return {
        filename: `${filename}.${type}`,
        href: `data:application/${type === 'svg' ? 'text/xml' : 'bpmn20-xml'};charset=UTF-8,${encodedData}`,
        data: data
      }
    },
    // 加载本地文件
    importLocalFile() {
      const that = this
      const file = this.$refs.refFile.files[0]
      const reader = new FileReader()
      reader.readAsText(file)
      reader.onload = function() {
        that.openDiagram(this.result)
      }
    },
    /* ------------------------------------------------ refs methods ------------------------------------------------------ */
    downloadProcessAsXml() {
      this.downloadProcess('xml')
    },
    downloadProcessAsBpmn() {
      this.downloadProcess('bpmn')
    },
    downloadProcessAsSvg() {
      this.downloadProcess('svg')
    },
    processSimulation() {
      this.simulationStatus = !this.simulationStatus
      this.simulation && this.bpmnModeler.get('toggleMode').toggleMode()
    },
    processRedo() {
      this.bpmnModeler.get('commandStack').redo()
    },
    processUndo() {
      this.bpmnModeler.get('commandStack').undo()
    },
    processZoomIn(zoomStep = 0.1) {
      const newZoom = Math.floor(this.defaultZoom * 100 + zoomStep * 100) / 100
      if (newZoom > 4) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(this.defaultZoom)
    },
    processZoomOut(zoomStep = 0.1) {
      const newZoom = Math.floor(this.defaultZoom * 100 - zoomStep * 100) / 100
      if (newZoom < 0.2) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(this.defaultZoom)
    },
    processZoomTo(newZoom = 1) {
      if (newZoom < 0.2) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be less than 0.2')
      }
      if (newZoom > 4) {
        throw new Error('[Process Designer Warn ]: The zoom ratio cannot be greater than 4')
      }
      this.defaultZoom = newZoom
      this.bpmnModeler.get('canvas').zoom(newZoom)
    },
    processReZoom() {
      this.defaultZoom = 1
      this.bpmnModeler.get('canvas').zoom('fit-viewport', 'auto')
    },
    processRestart() {
      this.recoverable = false
      this.revocable = false
      this.createNewDiagram()
    },
    elementsAlign(align) {
      const Align = this.bpmnModeler.get('alignElements')
      const Selection = this.bpmnModeler.get('selection')
      const SelectedElements = Selection.get()
      if (!SelectedElements || SelectedElements.length <= 1) {
        this.$message.warning('请按住 Ctrl 键选择多个元素对齐')
        return
      }
      this.$confirm('自动对齐可能造成图形变形，是否继续？', '警告', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => Align.trigger(SelectedElements, align))
    },
    /* -----------------------------    方法结束     --------------------------------- */
    previewProcessXML() {
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        this.previewResult = xml
        this.previewType = 'xml'
        this.previewModelVisible = true
      })
    },
    previewProcessJson() {
      const newConvert = new X2JS()
      this.bpmnModeler.saveXML({ format: true }).then(({ xml }) => {
        const { definitions } = newConvert.xml2js(xml)
        if (definitions) {
          this.previewResult = JSON.stringify(definitions, null, 4)
        } else {
          this.previewResult = ''
        }

        this.previewType = 'json'
        this.previewModelVisible = true
      })
    }
  }
}
</script>

<style lang="scss">
/*左边工具栏以及编辑节点的样式*/
@import '~bpmn-js/dist/assets/diagram-js.css';
@import '~bpmn-js/dist/assets/bpmn-font/css/bpmn.css';
@import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-codes.css';
@import '~bpmn-js/dist/assets/bpmn-font/css/bpmn-embedded.css';
@import '~bpmn-js-color-picker/colors/color-picker.css';
/*右侧详情*/
@import '~bpmn-js-properties-panel/dist/assets/bpmn-js-properties-panel.css';

.containers{
  display: flex; // 新增，为了让右侧和左侧一起看到
  position: absolute;
  background-color: #ffffff;
  width: 100%;
  height: 100%;
  display: flex;
  .canvas{
    width: 100%;
    height: 100%;
  }
  .bjs-powered-by {
    display: none;
  }
}

.process-designer__header {
  width: 100%;
  min-height: 36px;
  .el-button {
    text-align: center;
  }
  .el-button-group {
    margin: 4px;
  }
  .el-tooltip__popper {
    .el-button {
      width: 100%;
      text-align: left;
      padding-left: 8px;
      padding-right: 8px;
    }
    .el-button:hover {
      background: rgba(64, 158, 255, 0.8);
      color: #ffffff;
    }
  }
  .align {
    position: relative;
    i {
      &:after {
        content: "|";
        position: absolute;
        transform: rotate(90deg) translate(200%, 60%);
      }
    }
  }
  .align.align-left i {
    transform: rotate(90deg);
  }
  .align.align-right i {
    transform: rotate(-90deg);
  }
  .align.align-top i {
    transform: rotate(180deg);
  }
  .align.align-bottom i {
    transform: rotate(0deg);
  }
  .align.align-center i {
    transform: rotate(90deg);
    &:after {
      transform: rotate(90deg) translate(0, 60%);
    }
  }
  .align.align-middle i {
    transform: rotate(0deg);
    &:after {
      transform: rotate(90deg) translate(0, 60%);
    }
  }
}

.djs-popup.color-picker .djs-popup-body {
    display: grid;
    grid: auto-flow / 1fr 1fr 1fr;
    margin: 0;
    padding: 0;
    width: 100%;
}
.djs-popup-body .entry {
    padding: 5px 7px;
}
.djs-popup .entry:before {
    vertical-align: middle;
    font-size: 20px;
    height: 17px;
    width: 17px;
}
</style>
